/**
 * 
 */
package com.unbiz.common;

import static com.unbiz.common.Assert.*;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * 处理异常的工具类。
 * 
 * @author <a href="mailto:xuchen06@baidu.com">xuc</a>
 * @version create on 2014年7月21日 上午2:24:07
 */
public abstract class ExceptionUtil {

    /**
     * 检查异常是否由指定类型的异常引起。
     * 
     * @param throwable 受检异常
     * @param causeType 指定类型
     * @return 如果是则返还<code>true</code>
     */
    public static boolean causedBy(Throwable throwable, Class<? extends Throwable> causeType) {
        assertNotNull(causeType, "causeType");

        Set<Throwable> causes = CollectionUtil.createHashSet();

        for (; throwable != null && !causeType.isInstance(throwable) && !causes.contains(throwable); throwable =
                throwable.getCause()) {
            causes.add(throwable);
        }

        return throwable != null && causeType.isInstance(throwable);
    }

    /**
     * 取得最根本的异常。
     * 
     * @param throwable 受检异常
     * @return 最根本的异常。
     */
    public static Throwable getRootCause(Throwable throwable) {
        List<Throwable> causes = getCauses(throwable, true);

        return causes.isEmpty() ? null : causes.get(0);
    }

    /**
     * 取得包括当前异常在内的所有的causes异常，按出现的顺序排列。
     * 
     * @param throwable 受检异常
     * @return 包括当前异常在内的所有的causes异常，按出现的顺序排列。
     */
    public static List<Throwable> getCauses(Throwable throwable) {
        return getCauses(throwable, false);
    }

    /**
     * 取得包括当前异常在内的所有的causes异常，按出现的顺序排列
     * 
     * @param throwable 受检异常
     * @param reversed 是否反向
     * @return 包括当前异常在内的所有的causes异常，按出现的顺序排列。
     */
    public static List<Throwable> getCauses(Throwable throwable, boolean reversed) {
        LinkedList<Throwable> causes = CollectionUtil.createLinkedList();

        for (; throwable != null && !causes.contains(throwable); throwable = throwable.getCause()) {
            if (reversed) {
                causes.addFirst(throwable);
            } else {
                causes.addLast(throwable);
            }
        }

        return causes;
    }

    /**
     * 将异常转换成<code>RuntimeException</code>。
     * 
     * @param exception 受检异常
     * @return to <code>RuntimeException</code
     */
    public static RuntimeException toRuntimeException(Exception exception) {
        return toRuntimeException(exception, null);
    }

    /**
     * 将异常转换成<code>RuntimeException</code>。
     * 
     * @param exception 受检异常
     * @param runtimeExceptionClass 转换异常的类型
     * @return to <code>RuntimeException</code
     */
    public static RuntimeException toRuntimeException(Exception exception,
            Class<? extends RuntimeException> runtimeExceptionClass) {
        if (exception == null) {
            return null;
        }

        if (exception instanceof RuntimeException) {
            return (RuntimeException) exception;
        }
        if (runtimeExceptionClass == null) {
            return new RuntimeException(exception);
        }

        RuntimeException runtimeException;

        try {
            runtimeException = runtimeExceptionClass.newInstance();
        } catch (Exception ee) {
            return new RuntimeException(exception);
        }

        runtimeException.initCause(exception);
        return runtimeException;

    }

    /**
     * 抛出Throwable，但不需要声明<code>throws Throwable</code>，区分<code>Exception</code> 或</code>Error</code>。
     * 
     * @param throwable 受检异常
     * @throws Exception
     */
    public static void throwExceptionOrError(Throwable throwable) throws Exception {
        if (throwable instanceof Exception) {
            throw (Exception) throwable;
        }

        if (throwable instanceof Error) {
            throw (Error) throwable;
        }

        throw new RuntimeException(throwable); // unreachable code
    }

    /**
     * 抛出Throwable，但不需要声明<code>throws Throwable</code>，区分 <code>RuntimeException</code>、<code>Exception</code>
     * 或</code>Error</code>。
     * 
     * @param throwable 受检异常
     */
    public static void throwRuntimeExceptionOrError(Throwable throwable) {
        if (throwable instanceof Error) {
            throw (Error) throwable;
        }

        if (throwable instanceof RuntimeException) {
            throw (RuntimeException) throwable;
        }

        throw new RuntimeException(throwable);
    }

    /**
     * 取得异常的stacktrace字符串。
     * 
     * @param throwable 受检异常
     * @return stacktrace字符串
     */
    public static String getStackTrace(Throwable throwable) {
        StringWriter buffer = new StringWriter();
        PrintWriter out = new PrintWriter(buffer);

        throwable.printStackTrace(out);
        out.flush();

        return buffer.toString();
    }
    
    /**
     * Ensures the truth of an expression involving the state of the calling instance, but not
     * involving any parameters to the calling method.
     *
     * @param expression a boolean expression
     * @param errorMessage the exception message to use if the check fails; will be converted to a
     *     string using {@link String#valueOf(Object)}
     * @throws IllegalStateException if {@code expression} is false
     */
    public static void checkState(boolean expression, Object errorMessage) {
      if (!expression) {
        throw new IllegalStateException(String.valueOf(errorMessage));
      }
    }
    
    /**
     * Ensures that {@code index} specifies a valid <i>element</i> in an array, list or string of size
     * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive.
     *
     * @param index a user-supplied index identifying an element of an array, list or string
     * @param size the size of that array, list or string
     * @param desc the text to use to describe this index in an error message
     * @return the value of {@code index}
     * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size}
     * @throws IllegalArgumentException if {@code size} is negative
     */
    public static int checkElementIndex(int index, int size) {
      // Carefully optimized for execution by hotspot (explanatory comment above)
      if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException(badElementIndex(index, size, ""));
      }
      return index;
    }
      
    /**
     * Ensures that an object reference passed as a parameter to the calling method is not null.
     *
     * @param reference an object reference
     * @return the non-null reference that was validated
     * @throws NullPointerException if {@code reference} is null
     */
    public static <T> T checkNotNull(T reference) {
      if (reference == null) {
        throw new NullPointerException();
      }
      return reference;
    }
    private static String badElementIndex(int index, int size, String desc) {
	    if (index < 0) {
	      return String.format("%s (%s) must not be negative", desc, index);
	    } else if (size < 0) {
	      throw new IllegalArgumentException("negative size: " + size);
	    } else { // index >= size
	      return String.format("%s (%s) must be less than size (%s)", desc, index, size);
	    }
	  }
}
